#include "RWLock.h"
using namespace std;

RWLock::RWLock(){
		/// Inicializo el mutex de las variables.
	pthread_mutex_init(&mutex_unico,NULL); 
		/// Incialmente no hay nadie leyendo.
	escribiendo = false; 
		/// Incialmente no hay nadie leyendo.
	leyendo = 0; 
		/// Considero que los id's van de 0 en adelante.
	id = 0; 
}

void RWLock::rlock(){
		/// Pido el mutex para acceder a las variables compartidas
	pthread_mutex_lock(&mutex_unico);
		/// Si hay alguien esperando (o sea, hay alguien en la cola)
		/// o hay alguien esta escribiendo.
	if(!cola.empty() || escribiendo){
			/// Voy a tener que esperar.
			/// Luego pusheo un semaforo para que me avisen cuando puedo leer.
		pthread_cond_t * mi_sem = new pthread_cond_t;
		pthread_cond_init(mi_sem, NULL);
		trupla trup;
			id++;
				/* Establezco los datos del lector:
					* id = uno mas que el ultimo
					* modo = lectura
					* semaforo = el que cree recien 
				 */
			trup.id = id;
			trup.modo = r;
			trup.sem = mi_sem;
			/// Lo agrego a la cola.
		cola.push_back(trup);
			/// Espero para poder leer.
			/* Tengo que tener cuidado por los despertares espurios,
			 * entonces cuando vuelvo tengo que ver que se cumple el estado
			 * que tiene que tener para que vuelva correctamente:
			 * que no haya nadie escribiendo y que "este para leer".
			*/
		while( escribiendo || !estoyParaLeer(trup.id) )
			pthread_cond_wait(mi_sem, &mutex_unico);
			
			/// Ya puedo leer.
			/// Me saco de la cola.
		sacarDeLaCola(trup.id);
			/// Hay alguien mas leyendo.
		leyendo++;
			/// Termine de revisar todo, puedo devolverlo.
		pthread_mutex_unlock(&mutex_unico);
	}else{	
			/// Puedo leer.
			/// Hay alguien mas leyendo.
		leyendo++;
			/// Termine de revisar todo, puedo devolverlo.
		pthread_mutex_unlock(&mutex_unico);
	}
}
		
void RWLock::sacarDeLaCola (int id){
	list<trupla>::iterator iterator;
	int i;
		/// Recorro la cola buscando a id y lo saco.
	for (i=0, iterator = cola.begin(); iterator != cola.end(); ++iterator, ++i) {
		if((*iterator).id == id) {
			delete((*iterator).sem);
			cola.erase(iterator);
			break;
		}
	}
}

bool RWLock::estoyParaLeer(int id){
	list<trupla>::const_iterator iterator;
		/// Recorro la cola fijandome si entre id y el principio, hay algun escritor.
	for (iterator = cola.begin(); iterator != cola.end(); ++iterator) {
		if((*iterator).id == id) return true;
		else if((*iterator).modo == w) return false;
	}
	return false;
}

void RWLock::wlock(){
		/// Pido el mutex ya que necesito acceder a variables compartidas.
	pthread_mutex_lock(&mutex_unico);
	///pregunto si puedo escribir inmediatamente (esto se da si no hay nadie leyendo ni escribiendo y si la cola esta vacia)
		/// Si no hay nadie leyendo ni escribiendo y la cola esta vacia.
	if(leyendo==0 && cola.empty() && !escribiendo){
		/// Puedo leer.
			/// Indico que hay alguien escribiendo.
		escribiendo = true;
			/// Devuelvo el mutex.
		pthread_mutex_unlock(&mutex_unico);
	}else{
		/// No puedo leer (aun).
			/// Tengo que crear un semaforo para que me avisen cuando puedo escribir.
		pthread_cond_t* mi_sem = new pthread_cond_t;
		pthread_cond_init(mi_sem, NULL);
		trupla trup;
				/* Establezco los datos del escritor:
					* id = uno mas que el ultimo
					* modo = escritura
					* semaforo = el que cree recien 
				 */
			id++;
			trup.id = id;
			trup.modo = w;
			trup.sem = mi_sem;
			/// Lo agrego a al cola.
		cola.push_back(trup);
			/// Espero para poder escribir.
				/* Tengo que tener cuidado por los despertares espurios,
				* entonces cuando vuelvo tengo que ver que se cumple el estado
				* que tiene que tener para que vuelva correctamente:
				* que no haya nadie escribiendo ni leyendo y que "este para escribir".
				*/
		while( escribiendo || leyendo!=0 || trup.id != cola.front().id ){
			pthread_cond_wait(mi_sem, &mutex_unico);
		}
			/// Puedo escribir.
			/// Me salgo de la cola.
		delete(cola.front().sem);
		cola.pop_front();
			/// Indico que hay alguien escribiendo.
		escribiendo = true;
			/// Devuelvo el mutex.
		pthread_mutex_unlock(&mutex_unico);
	}
}

void RWLock::runlock(){
	pthread_mutex_lock(&mutex_unico);
		/// Pido el mutex ya que voy a acceder a variables compartidas.
		/// Resto uno a la variable leyendo ya que dejo de leer.
	leyendo--;
	///pregunto si no hay nadie mas leyendo(o sea "yo" era el ultimo) y si la cola no es vacia para mandarle la señal al primero de la cola (el cual seguro es un write)
		/// Si era el ultimo lector y hay alguien en la cola 
		/// entonces el primero de la misma debe ser un escritor.
		/// Luego le aviso que puede escribir.
	if(leyendo==0 && !cola.empty()){
		pthread_cond_signal(cola.front().sem);
	}
		/// Devuelvo el mutex.
	pthread_mutex_unlock(&mutex_unico);
}

void RWLock::wunlock() {
		/// Pido el mutex ya que voy a acceder a variables compartidas.
	pthread_mutex_lock(&mutex_unico);
		/// Indico que ya no hay nadie escribiendo.
	escribiendo = false;
		/// Si hay alguien en la cola.
	if(!cola.empty()){
			/// Si el primeror es un lector.
		if(cola.front().modo == r){
			list<trupla>::iterator iterator;
			iterator = cola.begin();
				/// Recorro la cola enviando señales a todos los lectores.
				/// Cuando encuentro un escritor termino.
			while(iterator!= cola.end() && (*iterator).modo != w){
				pthread_cond_signal(iterator->sem);
				iterator++;
			}
				/// Verifico el ultimo elemento por separado.
			if(iterator == cola.end()){
				if((*iterator).modo == r) pthread_cond_signal(iterator->sem);
			}
		}else{
			/// Sino, el primero es un escritor.
			/// Le aviso que puede escribir.
			pthread_cond_signal(cola.front().sem);
		}
	}
	pthread_mutex_unlock(&mutex_unico);
}
